/**
 * Copyright (C) 2010 Cloudfarming <info@cloudfarming.nl>
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.cloudfarming.client.geoviewer.jxmap.render;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import nl.cloudfarming.client.geoviewer.Palette;
import nl.cloudfarming.client.geoviewer.ValueRange;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.ViewType;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.filter.expression.ExpressionAbstract;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.renderer.lite.gridcoverage2d.GridCoverageRenderer;
import org.geotools.styling.ChannelSelection;
import org.geotools.styling.ColorMap;
import org.geotools.styling.ColorMapEntry;
import org.geotools.styling.ColorMapEntryImpl;
import org.geotools.styling.ContrastEnhancement;
import org.geotools.styling.RasterSymbolizer;
import org.geotools.styling.SLD;
import org.geotools.styling.SelectedChannelType;
import org.geotools.styling.Style;
import org.geotools.styling.StyleFactory;
import org.jdesktop.swingx.JXMapViewer;
import org.jdesktop.swingx.mapviewer.GeoPosition;
import org.opengis.filter.expression.ExpressionVisitor;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.operation.TransformException;
import org.openide.util.Exceptions;

/**
 * GridCoverage2DRenderer
 *
 *
 * @author Timon Veenstra
 */
public class GridCoverage2DRenderer implements RasterRenderer<GridCoverage2D> {

    /**
     * this gets rid of exception for not using native acceleration
     */
    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    private StyleFactory sf = CommonFactoryFinder.getStyleFactory(null);

    @Override
    public void paint(GridCoverage2D raster, JXMapViewer mapViewer, Graphics2D graphics, Palette palette) {
        // skip painting is raster is empty
        if (raster == null) {
            return;
        }

//------------------------------------------------------------------------------
//-- S-1 working solution, shows black and white and with extra fields
//------------------------------------------------------------------------------

//        RenderedImage renderedImage = raster.getRenderedImage();
//
//        GridEnvelope gridEnvelope = new GeneralGridEnvelope(renderedImage, raster.getDimension());
//        Envelope userEnv = new GeneralEnvelope(clientArea);
//        GridToEnvelopeMapper envelopeMapper = new GridToEnvelopeMapper(gridEnvelope, userEnv);
//        graphics.drawRenderedImage(renderedImage, envelopeMapper.createAffineTransform());


//------------------------------------------------------------------------------
//-- /S-1
//------------------------------------------------------------------------------


//------------------------------------------------------------------------------
//-- /S-2 shows black white image on map, symbolizer need to be enhanced
//------------------------------------------------------------------------------
        try {
//            com.vividsolutions.jts.geom.Envelope mapEnvelope = RendererUtilities.createMapEnvelope(mapViewer.getVisibleRect(), envelopeMapper.createAffineTransform());

            double minx = raster.getEnvelope2D().getMinX();
            double maxx = raster.getEnvelope2D().getMaxX();
            double miny = raster.getEnvelope2D().getMinY();
            double maxy = raster.getEnvelope2D().getMaxY();


            final Point2D upLeft = mapViewer.getTileFactory().geoToPixel(new GeoPosition(miny, minx), mapViewer.getZoom());
            final Point2D downRight = mapViewer.getTileFactory().geoToPixel(new GeoPosition(maxy, maxx), mapViewer.getZoom());

            Rectangle bounds = new Rectangle(0, 0, -1, -1);

            int x = (int) (upLeft.getX() - mapViewer.getViewportBounds().getX());
            int y = (int) (upLeft.getY() - mapViewer.getViewportBounds().getY());

            bounds.setLocation(x, y);

            x = (int) (upLeft.getX() - mapViewer.getViewportBounds().getX());
            y = (int) (downRight.getY() - mapViewer.getViewportBounds().getY());

            bounds.add(x, y);

            x = (int) (downRight.getX() - mapViewer.getViewportBounds().getX());
            y = (int) (downRight.getY() - mapViewer.getViewportBounds().getY());

            bounds.add(x, y);

            x = (int) (downRight.getX() - mapViewer.getViewportBounds().getX());
            y = (int) (upLeft.getY() - mapViewer.getViewportBounds().getY());

            bounds.add(x, y);


            com.vividsolutions.jts.geom.Envelope rasterEnvelope = new com.vividsolutions.jts.geom.Envelope(minx, maxx, miny, maxy);


//            RenderedImage renderedImage = raster.getRenderedImage();
//            renderedImage.getColorModel();
//            ImageWorker worker = new ImageWorker(renderedImage);
//            worker.makeColorTransparent(Color.WHITE);
//            assert renderedImage.getColorModel().getTransparency() == ColorModel.TRANSLUCENT;

//            Envelope userEnv = new GeneralEnvelope(bounds);
//            GridEnvelope gridEnvelope = new GeneralGridEnvelope(renderedImage, raster.getDimension());
//            GridToEnvelopeMapper envelopeMapper = new GridToEnvelopeMapper(gridEnvelope, userEnv);



            GridCoverageRenderer renderer = new GridCoverageRenderer(DefaultGeographicCRS.WGS84, rasterEnvelope, bounds, (AffineTransform) null);


            RasterSymbolizer symbolizer = sf.getDefaultRasterSymbolizer();

            symbolizer.setOpacity(new OpacityExp(0.9f));

//            RasterSymbolizer symbolizer = sb.createRasterSymbolizer(sb.createColorMap(new String[]{"","",""}, new double[]{0.1,0.5,0.7}, new Color[]{new Color(0xE32600),new Color(0xFC9F00),new Color(0x69E003)},  ColorMap.TYPE_VALUES), 1.0);
//            symbolizer.setOpacity(new OpacityExp(0.5));

            if (palette instanceof ValueRange) {
                ValueRange range = (ValueRange)palette;
                Number min = range.getMinValue();
                Number max = range.getMaxValue();
                double step = (max.doubleValue()-min.doubleValue())/range.getSteps();
                for (int s=0;s<range.getSteps();s++){
                    Integer value = new Double(min.doubleValue()+(s*step)).intValue();
                    Color c = palette.getColorForValue(value);
                    symbolizer.getColorMap().addColorMapEntry(getEntry(c,value.intValue()));
                }

            } else {
                //
                //fallback
                //
                symbolizer.getColorMap().addColorMapEntry(getEntry(new Color(0xFF000000, true), 1));
                symbolizer.getColorMap().addColorMapEntry(getEntry(new Color(0xFF8F050C, true), 100));
                symbolizer.getColorMap().addColorMapEntry(getEntry(new Color(0xFFDB0F01, true), 200));
                symbolizer.getColorMap().addColorMapEntry(getEntry(new Color(0xFFE65E00, true), 300));
                symbolizer.getColorMap().addColorMapEntry(getEntry(new Color(0xFFFC9F00, true), 400));
                symbolizer.getColorMap().addColorMapEntry(getEntry(new Color(0xFFFAD007, true), 500));
                symbolizer.getColorMap().addColorMapEntry(getEntry(new Color(0xFFDCF40A, true), 600));
                symbolizer.getColorMap().addColorMapEntry(getEntry(new Color(0xFF92FC00, true), 700));
                symbolizer.getColorMap().addColorMapEntry(getEntry(new Color(0xFF41C405, true), 800));
                symbolizer.getColorMap().addColorMapEntry(getEntry(new Color(0xFF048608, true), 900));
                symbolizer.getColorMap().addColorMapEntry(getEntry(new Color(0xFF044606, true), 1000));
            }
            symbolizer.getColorMap().setType(ColorMap.TYPE_RAMP);


//RasterSymbolizerHelper helper = new RasterSymbolizerHelper(raster, null);
//helper.execute();
            renderer.paint(graphics, raster.view(ViewType.GEOPHYSICS), symbolizer);
        } catch (FactoryException ex) {
            Exceptions.printStackTrace(ex);
        } catch (TransformException ex) {
            Exceptions.printStackTrace(ex);
        } catch (NoninvertibleTransformException ex) {
            Exceptions.printStackTrace(ex);
        }
//------------------------------------------------------------------------------
//-- /S-2
//------------------------------------------------------------------------------


//        RenderableImage renderableImage = raster.getRenderableImage(1,0);
//        RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//        RenderedImage renderedImage = renderableImage.createScaledRendering(clientArea.width, clientArea.height, hints);
//
//
////        try {
//
////        ImageWorker iw = new ImageWorker(raster.getRenderedImage());
//
////        PlanarImage pi = iw.getPlanarImage();
//
//
//
//
//        GeoPosition upleftpos = new GeoPosition(geometry.getCoordinate().y, geometry.getCoordinate().x);
//        final Point2D pointOnMap = mapViewer.getTileFactory().geoToPixel(upleftpos, mapViewer.getZoom());
//
//        Rectangle viewportBounds = mapViewer.getViewportBounds();
//
//        final int x = (int) (pointOnMap.getX() - viewportBounds.getX());
//        final int y = (int) (pointOnMap.getY() - viewportBounds.getY());
//
//        final int z = 10 / mapViewer.getZoom();
//
//
////        AffineTransform transform = new AffineTransform(z, 0, 0, z, x, y);
////        AffineTransform transform = new XAffineTransform(z, 1.0000000000000000001, 0.999999999999999999999, z, x, y);
//        AffineTransform transform = RendererUtilities.worldToScreenTransform(ReferencedEnvelope.EVERYTHING, clientArea);
//
//        RenderingHints hints = new Hints();
//
//        Envelope envelope = new Envelope(geometry.getCoordinate());
//        try {
//            GridCoverageRenderer gcr = new GridCoverageRenderer(DefaultGeographicCRS.WGS84, envelope, clientArea, hints);
//            gcr.paint(graphics, raster, new RasterSymbolizerImpl());
//        } catch (FactoryException ex) {
//            Exceptions.printStackTrace(ex);
//        } catch (TransformException ex) {
//            Exceptions.printStackTrace(ex);
//        } catch (NoninvertibleTransformException ex) {
//            Exceptions.printStackTrace(ex);
//        }

//        AffineTransform transform = AffineTransform.getTranslateInstance(x, y);

//        AffineTransform.getScaleInstance(mapViewer.getZoom(), mapViewer.getZoom());

//        graphics.drawRenderedImage(raster.getRenderedImage(), transform);
//            WarpTransform2D warpTransform = new WarpTransform2D(srcCoords, dstCoords, 0);
//
////            Geometry e2d = geometry.getEnvelope();
//            Envelope envelope = new Envelope(e2d.getCoordinate());
//            GridCoverageRenderer internal = new GridCoverageRenderer(DefaultGeographicCRS.WGS84, envelope , mapViewer.getBounds(), new AffineTransform() );
//            internal.paint(graphics, raster, new RasterSymbolizerImpl());
//        } catch (FactoryException ex) {
//            Exceptions.printStackTrace(ex);
//        } catch (TransformException ex) {
//            Exceptions.printStackTrace(ex);
//        } catch (NoninvertibleTransformException ex) {
//            Exceptions.printStackTrace(ex);
//        }
    }

    private ColorMapEntry getEntry(Color color, int value) {
        ColorMapEntry entry = new ColorMapEntryImpl();
        entry.setColor(new ColorExp(color));
        entry.setQuantity(new ValueExp(value));
        return entry;
    }

    private class ColorExp extends ExpressionAbstract {

        private final Color color;

        public ColorExp(Color color) {
            this.color = color;
        }

        @Override
        public Object evaluate(Object object) {
            return this.color;
        }

        @Override
        public Object accept(ExpressionVisitor visitor, Object extraData) {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    }

    private class ValueExp extends ExpressionAbstract {

        private final int value;

        public ValueExp(int value) {
            this.value = value;
        }

        @Override
        public Object evaluate(Object object) {
            return Integer.valueOf(this.value);
        }

        @Override
        public Object accept(ExpressionVisitor visitor, Object extraData) {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    }

    private class OpacityExp extends ExpressionAbstract {

        private final double opacity;

        public OpacityExp(double opacity) {
            assert opacity <= 1.0 && opacity > 0.0;
            this.opacity = opacity;
        }

        @Override
        public Object evaluate(Object object) {
            return new Double(this.opacity);
        }

        @Override
        public Object accept(ExpressionVisitor visitor, Object extraData) {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    }

    /**
     * Create a Style to display the specified band of the GeoTIFF image
     * as a greyscale layer.
     * <p>
     * This method is a helper for createGreyScale() and is also called directly
     * by the displayLayers() method when the application first starts.
     *
     * @param band the image band to use for the greyscale display
     *
     * @return a new Style instance to render the image in greyscale
     */
    private Style createGreyscaleStyle(int band) {
        ContrastEnhancement ce = sf.createContrastEnhancement();
        SelectedChannelType sct = sf.createSelectedChannelType(String.valueOf(band), ce);

        RasterSymbolizer sym = sf.getDefaultRasterSymbolizer();
        ChannelSelection sel = sf.createChannelSelection(new SelectedChannelType[]{sct});
        sym.setChannelSelection(sel);
        sym.setOpacity(new OpacityExp(0.5));

        return SLD.wrapSymbolizers(sym);
    }

    /*
    Rectangle viewportBounds = mapViewer.getViewportBounds();


    GeoPosition geopoint = new GeoPosition(raster.getEnvelope2D().getMinX(), raster.getEnvelope2D().getMinY());
    Point2D point2D = mapViewer.getTileFactory().geoToPixel(geopoint, mapViewer.getZoom());
    int x = (int) (point2D.getX() - viewportBounds.getX());
    int y = (int) (point2D.getY() - viewportBounds.getY());

    ColorModel cm = new PaletteColorModel(palette);
    BufferedImage image = new BufferedImage(cm, (WritableRaster) raster.getRenderedImage().getData(), false, null);

    graphics.drawImage(image, x, y, new ImageObserver() {

    @Override
    public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
    return true;
    }
    });
     *
     */
}
